#region (c) 2014 THN Solutions LLC. - All rights reserved
/*
Simplified BSD License (BSD)
Copyright (c) 2014, THN Solutions LLC. ( www.ThnSolutions.com )
Author: Nguyễn, M. Hải                 ( www.Minh-Hai.com     )
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, this 
      list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright notice, 
      this list of conditions and the following disclaimer in the documentation 
      and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/
#endregion

#region Record of Change
//             							CHANGE HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | DEVELOPER  | DESCRIPTION                                              |
// -------------------------------------------------------------------------------------
// | 09-Nov-09 | LamNPT      | First creation.                                          |
// -------------------------------------------------------------------------------------
//
#endregion

#region Record of Audit
//             							REVIEW HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | REVIEWER   | NOTES                                                    |
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
//
#endregion

#region Using directives
using System;

using Thn;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;
#endregion

namespace Thn.Interface.Vcl
{
    /// <summary>
    /// Scroller bar control.
    /// </summary>
    [ToolboxItem(true), DefaultProperty("Value"), DefaultEvent("Scroll"), ToolboxBitmap(typeof(ResourceBeacon), "Thn.Interface.Vcl.Resources.Icons.Scroller.bmp")]
#if DEBUG 
    public
#else
    public
#endif
    class Scroller : ControlBase
    {
        #region Events

        #region Scroll
        /// <summary>
        /// This event is raised after scroll the handler bar.
        /// </summary>
        public event System.Windows.Forms.ScrollEventHandler Scroll;

        /// <summary>
        /// Invoke event Scroll.
        /// </summary>
        protected void RaiseScroll(System.Windows.Forms.ScrollEventArgs args)
        {
            System.Windows.Forms.ScrollEventHandler handler = Scroll;
            if (!mSuspendScrollEvent && handler != null)
            {
                handler(this, args);
            }
            #region Defensive tracing
            else 
            {
                // the event hadler is not register or suspend mode, do not raise event.
            }
            #endregion
        }
        #endregion

        #region Dialog Key Pressed
        /// <summary>
        /// This event is raised after user press on dialog key.
        /// </summary>
        public event ProcessDialogKeyHandler DialogKeyPressed;

        /// <summary>
        /// Invoke event DialogKeyPressed.
        /// </summary>
        protected bool RaiseDialogKeyPressed(Keys keyData)
        {
            bool result = false;

            ProcessDialogKeyHandler handler = DialogKeyPressed;
            if (handler != null)
            {
                result = handler(this, keyData);
            }
            #region Defensive tracing
            else 
            {
                // the event haldler is not registered, do nothing.
            }
            #endregion

            return result;
        }
        #endregion

        #endregion

        #region Fields

        #region Mouse Handler Flags
        /// <summary>
        /// Determines whether mouse button is pressed down or not.
        /// </summary>
        protected bool mIsDown = false;
        /// <summary>
        /// Determines whether mouse pointer move out of the control or not.
        /// </summary>
        protected bool mIsOut = false;
        /// <summary>
        /// Determines whether user is click on track region or not. 
        /// </summary>
        protected bool mIsLargeChange = false;

        /// <summary>
        /// Store current hit region of the control.
        /// </summary>
        ScrollerHitRegion mCurrentHit = ScrollerHitRegion.Other;
        /// <summary>
        /// Old x-coordinate of mouse pointer.
        /// </summary>
        int mOldX = 0;
        /// <summary>
        /// Old y-coordinate of mouse pointer.
        /// </summary>
        int mOldY = 0;
        /// <summary>
        /// Current x-coordinate of mouse pointer.
        /// </summary>
        int mTargetX = 0;
        /// <summary>
        /// Current y-coordinate of mouse pointer.
        /// </summary>
        int mTargetY = 0;

        /// <summary>
        /// I, the developer TLN, dont know what this field is used for.
        /// </summary>
        bool mUpDown = false;
        #endregion

        #region Others
        /// <summary>
        /// Indicate that the scroller is stop or not.
        /// </summary>
        bool mIsStopScroll = true;
        /// <summary>
        /// The distance between third button and handler button.
        /// </summary>
        float mStartRange = 0;
        /// <summary>
        /// The distance between handler button and end button.
        /// </summary>
        float mEndRange = 0;
        /// <summary>
        /// The percent factor between the value and length of control.
        /// </summary>
        float mLengthFactor = 0;

        int mMinRange = 0;
        int mMaxRange = 0;
        #endregion

        #endregion

        #region Properties

        #region Value
        private int mValue = 25;
        /// <summary>
        /// Gets/Sets current value.
        /// </summary>
        [Browsable(true), DefaultValue(25), Category("Value"), Description("Current value")]
        public int Value
        {
            get { return mValue; }
            set
            {
                mValue = value;
                if (mValue < mMinValue) mValue = mMinValue;
                else if (mValue > mMaxValue) mValue = mMaxValue;
                #region Defensive tracing
                else
                {
                    //valid, move on
                }
                #endregion

                InvalidateLayout();
            }
        }
        #endregion

        #region Min Value
        private int mMinValue = 0;
        /// <summary>
        /// Gets/Sets minimum value.
        /// </summary>
        [Browsable(true), DefaultValue(0), Category("Value"), Description("Miminum value")]
        public int MinValue
        {
            get { return mMinValue; }
            set
            {
                mMinValue = value;
                if (mMinValue >= mMaxValue) mMinValue = mMaxValue - 1;
                #region Defensive tracing
                else
                {
                    //valid case
                }
                #endregion

                //ensure value is in valid range
                if (mValue < mMinValue) mValue = mMinValue;
                else if (mValue > mMaxValue) mValue = mMaxValue;
                #region Defensive tracing
                else
                {
                    //valid, move on
                }
                #endregion

                InvalidateLayout();
            }
        }
        #endregion

        #region Max Value
        private int mMaxValue = 100;
        /// <summary>
        /// Gets/Sets maximum value.
        /// </summary>
        [Browsable(true), DefaultValue(100), Category("Value"), Description("Maximum value")]
        public int MaxValue
        {
            get { return mMaxValue; }
            set
            {
                mMaxValue = value;

                if (mMaxValue <= mMinValue) mMaxValue = mMinValue + 1;
                #region Defensive tracing
                else
                {
                    //valid, move on
                }
                #endregion

                //ensure value is in valid range
                if (mValue < mMinValue) mValue = mMinValue;
                else if (mValue > mMaxValue) mValue = mMaxValue;
                #region Defensive tracing
                else
                {
                    //valid, move on
                }
                #endregion

                InvalidateLayout();
            }
        }
        #endregion

        #region Large Change
        int mLargeChange = 60;
        /// <summary>
        /// Gets or sets a value to be added from Value
        /// property when the scroll box is moved a large distance.
        /// </summary>
        [Browsable(true), DefaultValue(60), Category("Value"), Description("The distance to move the handler when user click on the track region of the control.")]
        public int LargeChange
        {
            get { return mLargeChange; }
            set
            {
                mLargeChange = value;

                InvalidateLayout();
            }
        }
        #endregion

        #region Small Change
        int mSmallChange = 1;
        /// <summary>
        /// Gets or sets the value to be added from the Value
        ///  property when the scroll box is moved a small distance.
        /// </summary>
        [Browsable(true), DefaultValue(1), Category("Value"), Description("The distance to move the handler when user click on the start button or end button of the control.")]
        public int SmallChange
        {
            get { return mSmallChange; }
            set 
            {
                mSmallChange = value;

                InvalidateLayout();
            }
        }
        #endregion

        #region Render Context
        private ScrollerRenderContext mRenderContext;
        /// <summary>
        /// Gets reference to current renderation context.
        /// </summary>
        protected ScrollerRenderContext RenderContext
        {
            get { return mRenderContext; }
        }
        #endregion

        #region Suspend Scroll Event
        /// <summary>
        /// Gets or Sets value that when it turn on, the scroll event will be not raised.
        /// </summary>
        protected bool mSuspendScrollEvent = false;
        /// <summary>
        /// Gets or Sets value that when it turn on, the scroll event will be not raised.
        /// </summary>
        [Browsable(true), DefaultValue(false), Category("Behavior"), Description("when turn this flag on, the scroll event will be not raised.")]
        public bool SuspendScrollEvent
        {
            get { return mSuspendScrollEvent; }
            set { mSuspendScrollEvent = value; }
        }
        #endregion  

        #region Allow Mouse Wheel
        private bool mAllowMouseWheel = true;
        /// <summary>
        /// Gets or Sets value of AllowMouseWheel
        /// </summary>
        [Browsable(true), DefaultValue(true), Category("Behavior"), Description("Allow or not process the control when center mouse button wheel.")]
        public bool AllowMouseWheel
        {
            get { return mAllowMouseWheel; }
            set { mAllowMouseWheel = value; }
        }
        #endregion  

        #region Show Thrid Button
        private bool mShowThirdButton = true;
        /// <summary>
        /// Gets or Sets value indicate whether the third button will be showed or not.
        /// </summary>
        [Browsable(true), DefaultValue(true), Category("Appearance"), Description("Indicate that the third button will be showed or not.")]
        public bool ShowThirdButton
        {
            get { return mShowThirdButton; }
            set 
            {
                mShowThirdButton = value;
                InvalidateLayout();
            }
        }
        #endregion  

        #region Show Third Button At Start
        /// <summary>
        /// Determines whether the third button will be display from the start or the end of the control.
        /// </summary>
        protected bool mShowThirdButtonAtStart = true;
        /// <summary>
        /// Gets or Sets value determines whether the third button will be display from the start or the end of the control.
        /// </summary>
        [Browsable(true), DefaultValue(true), Category("Appearance"), Description("Determines whether the third button will be display from the start or the end of the control.")]
        public bool ShowThirdButtonAtStart
        {
            get { return mShowThirdButtonAtStart; }
            set 
            {
                mShowThirdButtonAtStart = value;
                InvalidateLayout();
            }
        }
        #endregion  

        #region Minimum Handler Size
        private int mMinimumHandlerSize = 20;
        /// <summary>
        /// Gets or Sets minimum lenght of the handler button.
        /// </summary>
        [Browsable(true), DefaultValue(20), Category("Appearance"), Description("The minimum length of the handler button.")]
        public int MinimumHandlerSize
        {
            get { return mMinimumHandlerSize; }
            set { mMinimumHandlerSize = value; }
        }
        #endregion  

        #region Layout Style
        private ScrollerStyle mLayoutStyle = ScrollerStyle.Horizontal;
        /// <summary>
        /// Gets or Sets Dimension of scroller indicate that the scroller is Horizontal scroller or Vertical scroller.
        /// </summary>
        [Browsable(true), DefaultValue(ScrollerStyle.Horizontal), Category("Appearance"), Description("Dimension of scroller indicate that the scroller is Horizontal scroller or Vertical scroller.")]
        public ScrollerStyle LayoutStyle
        {
            get { return mLayoutStyle; }
            set 
            {
                mLayoutStyle = value;

                int temp = Width;
                Width = Height;
                Height = temp;

                InvalidateLayout();
            }
        }
        #endregion  

        #endregion

        //==========

        #region Base methods

        #region On Calculate Layout
        /// <summary>
        /// Perform layout calculations neccessary for this control.
        /// </summary>
        protected override void OnCalculateLayout()
        {
            RectangleF r = new RectangleF(0, 0, (float)Width, (float)Height);
            RectangleF rStart, rEnd, rThird, rTrack, rHandler;
            RectangleF rStartArrow = RectangleF.Empty, rEndArrow = RectangleF.Empty, rThirdArrow = RectangleF.Empty;

            base.OnCalculateLayout();

            #region Horizontal scroller
            if (mLayoutStyle == ScrollerStyle.Horizontal)
            {
                rTrack = r;

                int updownWidth;
                if (this.Height > 30)
                    updownWidth = 30;
                else
                    updownWidth = this.Height;

                rStart = new RectangleF(0, 0, updownWidth, r.Height);
                rEnd = new RectangleF(r.Width - rStart.Width, 0, updownWidth, r.Height);

                if (mShowThirdButton)
                {
                    if (mShowThirdButtonAtStart)
                        rThird = new RectangleF(rEnd.Left - updownWidth, 0, updownWidth, r.Height);
                    else
                        rThird = new RectangleF(rStart.Left + updownWidth, 0, updownWidth, r.Height);
                }
                #region Defensive tracing
                else
                {
                    rThird = RectangleF.Empty;
                }
                #endregion

                float trackWidth = rTrack.Width - updownWidth * 2 - rThird.Width;

                int handlerWidth = (int)((float)(mLargeChange * trackWidth) / (float)mMaxValue);

                if (handlerWidth < mMinimumHandlerSize)
                {
                    handlerWidth = mMinimumHandlerSize;
                }
                #region Defensive tracing
                else 
                {
                    // handler with value is valid.
                }
                #endregion

                mLengthFactor = (trackWidth - handlerWidth) / (float)(mMaxValue - mLargeChange + 1);

                float handlerLeft = rStart.Width + mValue * mLengthFactor;

                if (mShowThirdButton && !mShowThirdButtonAtStart)
                    handlerLeft += rThird.Width;

                rHandler = new RectangleF(handlerLeft, 0, handlerWidth, r.Height);

                if (mShowThirdButtonAtStart)
                {
                    mStartRange = rStart.Width;
                    mEndRange = rTrack.Width - (rEnd.Width + rThird.Width + rHandler.Width);
                }
                else
                {
                    mStartRange = rStart.Width + rThird.Width;
                    mEndRange = rTrack.Width - (rEnd.Width + rHandler.Width);
                }

                //Calulate the rectangle of the two triangles
                rStartArrow.Width = rStart.Width / 2;
                rStartArrow.Height = rStart.Height / 2;
                rStartArrow.Location = new PointF(rStart.Left + rStart.Width / 4, rStart.Top + rStart.Height / 4);

                rEndArrow.Width = rEnd.Width / 2;
                rEndArrow.Height = rEnd.Height / 2;
                rEndArrow.Location = new PointF(rEnd.Left + rEnd.Width / 4, rEnd.Top + rEnd.Height / 4);

                rThirdArrow.Width = rThird.Width / 2;
                rThirdArrow.Height = rThird.Height / 2;
                rThirdArrow.Location = new PointF(rThird.Left + rThird.Width / 4, rThird.Top + rThird.Height / 4);           
            }
            #endregion

            #region Vertical scroller
            else
            {
                rTrack = r;

                int updownWidth;
                if (this.Width > 30)
                    updownWidth = 30;
                else
                    updownWidth = this.Width;

                rStart = new RectangleF(0, 0, r.Width, updownWidth);
                rEnd = new RectangleF(0, r.Height - rStart.Height, r.Width, updownWidth);

                if (mShowThirdButton)
                {
                    if (mShowThirdButtonAtStart)
                        rThird = new RectangleF(0, rEnd.Top - updownWidth, r.Width, updownWidth);
                    else
                        rThird = new RectangleF(0, rStart.Top + updownWidth, r.Width, updownWidth);
                }
                else
                    rThird = new RectangleF(0, 0, 0, 0);

                float trackWidth = rTrack.Height - updownWidth * 2 - rThird.Height;


                float handlerWidth = (mLargeChange * trackWidth) / (float)mMaxValue;
                if (handlerWidth < mMinimumHandlerSize)
                    handlerWidth = mMinimumHandlerSize;

                mLengthFactor = (trackWidth - handlerWidth) / (float)(mMaxValue - mLargeChange + 1);


                float handlerLeft = rStart.Height + (mValue * mLengthFactor);

                if (mShowThirdButton && !mShowThirdButtonAtStart)
                    handlerLeft += rThird.Height;

                rHandler = new RectangleF(0, handlerLeft, r.Width, handlerWidth);

                if (mShowThirdButtonAtStart)
                {
                    mStartRange = rStart.Height;
                    mEndRange = rTrack.Height - (rEnd.Height + rThird.Height + rHandler.Height);
                }
                else
                {
                    mStartRange = rStart.Height + rThird.Height;
                    mEndRange = rTrack.Height - (rEnd.Height + rHandler.Height);
                }

                rStartArrow.Width = rStart.Width / 2;
                rStartArrow.Height = rStart.Height / 2;
                rStartArrow.Location = new PointF(rStart.Left + rStart.Width / 4, rStart.Top + rStart.Height / 4);

                rEndArrow.Width = rEnd.Width / 2;
                rEndArrow.Height = rEnd.Height / 2;
                rEndArrow.Location = new PointF(rEnd.Left + rEnd.Width / 4, rEnd.Top + rEnd.Height / 4);

                rThirdArrow.Width = rThird.Width / 2;
                rThirdArrow.Height = rThird.Height / 2;
                rThirdArrow.Location = new PointF(rThird.Left + rThird.Width / 4, rThird.Top + rThird.Height / 4);
                /*
                #region Calculate for button Up, Down, Third
                int updownWidth;
                if (this.Width > 30)
                    updownWidth = 30;
                else
                    updownWidth = this.Width;

                rStart = new RectangleF(0, 0, r.Width, updownWidth);
                rEnd = new RectangleF(0, r.Height - updownWidth, r.Width, updownWidth);

                if (mShowThirdButton)
                {
                    if (mRenderContext.ThirdButtonState == ButtonState.Normal)
                        rThird = new RectangleF(0, rEnd.Top - updownWidth, r.Width, updownWidth);
                    else
                        rThird = new RectangleF(0, rStart.Top + updownWidth, r.Width, updownWidth);
                }
                #region Defensive tracing
                else
                {
                    rThird = new RectangleF(0, 0, 0, 0);
                }
                #endregion
                #endregion

                #region Calculate for main bar
                rTrack = r;
                rTrack.X = r.X;
                rTrack.Y = r.Y + rStart.Height;
                rTrack.Width = r.Width;
                rTrack.Height = r.Height - rStart.Height - rEnd.Height;
                #endregion Calculate for main bar

                #region Calculate for handler

                mStartRange = rStart.Width;
                mEndRange = rTrack.Width - rHandler.Width + rEnd.Width;


                float trackWidth = rTrack.Height;
                int HandlerHeight = (int)((mLargeChange * trackWidth) / (float)mMaxValue);
                if (HandlerHeight < mMinimumHandlerSize)
                    HandlerHeight = mMinimumHandlerSize;
                mLengthFactor = (trackWidth - HandlerHeight) / (float)(mMaxValue - mLargeChange + 1);
                float HandlerLeft = rStart.Height + (mValue * mLengthFactor);

                rHandler = new RectangleF(0, HandlerLeft, r.Width, HandlerHeight);
                mStartRange = rStart.Height;
                mEndRange = rTrack.Height - rHandler.Height + rEnd.Height;


                #endregion Calculate for handler

                #region Calulate the rectangle of the two triangles
                rStartArrow.Width = rStart.Width / 2;
                rStartArrow.Height = rStart.Height / 2;
                rStartArrow.Location = new PointF(rStart.Left + rStart.Width / 4, rStart.Top + rStart.Height / 4);

                rEndArrow.Width = rEnd.Width / 2;
                rEndArrow.Height = rEnd.Height / 2;
                rEndArrow.Location = new PointF(rEnd.Left + rEnd.Width / 4, rEnd.Top + rEnd.Height / 4);

                rThirdArrow.Width = rThird.Width / 2;
                rThirdArrow.Height = rThird.Height / 2;
                rThirdArrow.Location = new PointF(rThird.Left + rThird.Width / 4, rThird.Top + rThird.Height / 4);
                #endregion
                */
            }
            #endregion

            #region Assign value to render context
            ScrollerRenderContext ct = mRenderContext;

            ct.StartRegion = rStart;
            ct.EndRegion = rEnd;
            ct.ThirdRegion = rThird;
            ct.HandlerRegion = rHandler;
            ct.TrackRegion = rTrack;

            ct.StartArrow = rStartArrow;
            ct.EndArrow = rEndArrow;
            ct.ThirdArrow = rThirdArrow;
            #endregion
        }
        #endregion

        #region On Get Render Context
        /// <summary>
        /// Get reference to the renderation context which is used by <see cref="IControlRenderer"/>.
        /// </summary>
        protected override RenderContext OnGetRenderContext(Graphics canvas)
        {
            return mRenderContext;
        }
        #endregion

        #region On Scroll
        /// <summary>
        /// This method will be called when user scroll the scroller.
        /// </summary>
        protected virtual void OnScroll(ScrollEventArgs e) { }
        #endregion

        #region On Dialog Key Pressed
        /// <summary>
        /// This method will be called after user click on a specific dialog key.
        /// </summary>
        protected virtual void OnDialogKeyPressed(Keys keyData) { }
        #endregion

        #endregion

        #region Public methods

        #region Set Value
        /// <summary>
        /// Sets value to scroll without raise event.
        /// </summary>
        public void SetValue(int value)
        {
            ScrollTo(value, false);
        }
        #endregion

        #region Scroll By
        /// <summary>
        /// Move current position by value.
        /// </summary>
        public void ScrollBy(int value)
        {
            int delta = value < 0 ? -1 : 1;
            int count = 0;
            while (count != value)
            {
                ScrollTo(mValue + mSmallChange * delta);
                count += delta;
            }
        }
        #endregion        

        #endregion

        #region Inner methods

        #region Do Scroll
        void DoScroll()
        {
            int change;
            int counter = 0;

            if (mIsLargeChange)
            {
                change = mLargeChange;
            }
            else
            {
                change = mSmallChange;
            }
            if (mUpDown)
            {
                change = -change;
            }
            #region Defensive tracing
            else 
            {
                // start button is not down, do nothing.
            }
            #endregion

            while (!mIsStopScroll)
            {
                if (mIsLargeChange)
                {
                    if (mRenderContext.HandlerRegion.Contains(mTargetX, mTargetY))
                    {
                        mIsStopScroll = true;
                        return;
                    }
                    #region Defensive tracing
                    else
                    {
                        // available scroll.
                    }
                    #endregion
                }
                #region Defensive tracing
                else
                {
                    // small change mode, do not process anything here.
                }
                #endregion

                if (counter < 20)
                {
                    ScrollTo(mValue + change);
                    Thread.Sleep(70);
                }
                else if (counter < 50)
                {
                    ScrollTo(mValue + change * 2);
                }
                else if (counter < 100) ScrollTo(mValue + change * 3);
                else ScrollTo(mValue + change * 10);

                counter++;

                System.Windows.Forms.Application.DoEvents();
            }

            mIsLargeChange = false;
        }
        #endregion

        #region Scroll To
        /// <summary>
        /// Scroll from current position to a specific value and alway raise event.
        /// </summary>
        void ScrollTo(int value) 
        {
            ScrollTo(value, true);
        }
        /// <summary>
        /// Scroll from current position to a specific value with or without
        /// raise event depends on isTriggerEvent parameter.
        /// </summary>
        void ScrollTo(int value, bool isTriggerEvent)
        {
            if (mValue != value)
            {
                RectangleF rHandler = mRenderContext.HandlerRegion;
                int oldValue = mValue;
                float handlerLeft;

                if (value <= mMinValue)
                {
                    mValue = mMinValue;
                    handlerLeft = mStartRange;
                }
                else if (value >= mMaxValue - mLargeChange + 1)
                {
                    handlerLeft = mEndRange;
                    mValue = mMaxValue - mLargeChange + 1;
                }
                else
                {
                    mValue = value;
                    handlerLeft = mStartRange + (mValue * mLengthFactor);
                }

                if (mLayoutStyle == ScrollerStyle.Horizontal)
                {
                    // This is a bug in drawing
                    if (mValue == mMinValue)
                        rHandler.X = handlerLeft;
                    else
                        rHandler.Location = new PointF(handlerLeft, rHandler.Top);
                }
                else
                {
                    rHandler.Location = new PointF(rHandler.Left, handlerLeft);
                }

                mRenderContext.HandlerRegion = rHandler;

                Invalidate();

                #region event processing
                if (isTriggerEvent)
                {
                    ScrollEventArgs arg;

                    if (mIsLargeChange && oldValue < Value)
                    {
                        arg = new ScrollEventArgs(ScrollEventType.LargeIncrement, oldValue, Value);
                        OnScroll(arg);
                        RaiseScroll(arg);
                    }
                    else if (mIsLargeChange && oldValue > Value)
                    {
                        arg = new ScrollEventArgs(ScrollEventType.LargeDecrement, oldValue, Value);
                        OnScroll(arg);
                        RaiseScroll(arg);
                    }
                    else if ((!mIsLargeChange && oldValue < Value))
                    {
                        arg = new ScrollEventArgs(ScrollEventType.SmallIncrement, oldValue, Value);
                        OnScroll(arg);
                        RaiseScroll(arg);
                    }
                    else if ((!mIsLargeChange && oldValue > Value))
                    {
                        arg = new ScrollEventArgs(ScrollEventType.SmallDecrement, oldValue, Value);
                        OnScroll(arg);
                        RaiseScroll(arg);
                    }
                    #region Defensive tracing
                    else 
                    {
                        // invalid value, do not raise event here.
                    }
                    #endregion
                }
                #endregion
            }
            #region Defensive tracing
            else
            {
                // reach destination, stop scroll.
            }
            #endregion
        }
        #endregion

        #region Hit Test
        /// <summary>
        /// Check position of the mouse pointer on the control.
        /// </summary>
        /// <param name="x">x - coordinate.</param>
        /// <param name="y">y - coordinate.</param>
        /// <returns>A region define by <see cref="HitRegion"/> enum.</returns>
        protected virtual ScrollerHitRegion HitTest(int x, int y)
        {
            ScrollerHitRegion result = ScrollerHitRegion.Other;

            ScrollerRenderContext rc = mRenderContext;
            if (rc.StartRegion.Contains(x, y)) result = ScrollerHitRegion.Start;
            else if (rc.EndRegion.Contains(x, y)) result = ScrollerHitRegion.End;
            else if (rc.ThirdRegion.Contains(x, y)) result = ScrollerHitRegion.Third;
            else if (rc.HandlerRegion.Contains(x, y)) result = ScrollerHitRegion.Handler;
            else if (rc.TrackRegion.Contains(x, y)) result = ScrollerHitRegion.Track;
            else
            {
                result = ScrollerHitRegion.Other;
            }

            return result;
        }
        #endregion

        #endregion

        //==========

        #region Process Mouse Events

        #region Move
        /// <summary>
        /// Override this method for process the control when mouse pointer move into it.
        /// </summary>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            #region Handler button pressed
            if (mRenderContext.HandlerState == ButtonState.Pressed)
            {
                int oldValue = mValue;
                float newPos;
                bool mustRaiseEvent = true;

                #region IsHorizontal
                if (mLayoutStyle == ScrollerStyle.Horizontal)
                {
                    if (mIsOut && (e.X < mMinRange || e.X > mMaxRange))
                    {
                        mustRaiseEvent = false;
                    }
                    else
                    {
                        newPos = mRenderContext.HandlerRegion.Left + (float)e.X - (float)mOldX;

                        if (newPos < mStartRange)
                        {
                            RectangleF r = mRenderContext.HandlerRegion;
                            r.Location = new PointF(mStartRange, r.Top);
                            mRenderContext.HandlerRegion = r;
                            mValue = mMinValue;
                            mMinRange = e.X;
                            mIsOut = true;
                        }
                        else if (newPos > mEndRange)
                        {
                            RectangleF r = mRenderContext.HandlerRegion;
                            r.Location = new PointF(mEndRange, r.Top);
                            mRenderContext.HandlerRegion = r;
                            mValue = mMaxValue - LargeChange + 1;
                            mMaxRange = e.X;
                            mIsOut = true;
                        }
                        else
                        {
                            RectangleF r = mRenderContext.HandlerRegion;
                            r.Location = new PointF(newPos, r.Top);
                            mRenderContext.HandlerRegion = r;
                            mValue = (int)((newPos - mStartRange) / mLengthFactor);
                            mIsOut = false;
                        }
                        mOldX = e.X;
                    }
                }//HScrollbar
                #endregion IsHorizontal

                #region IsVertical
                else
                {//VScrollbar
                    if (mIsOut && (e.Y < mMinRange || e.Y > mMaxRange))
                    {
                        mustRaiseEvent = false;
                    }
                    else
                    {
                        newPos = mRenderContext.HandlerRegion.Top + (float)e.Y - (float)mOldY;

                        if (newPos < mStartRange)
                        {
                            RectangleF r = mRenderContext.HandlerRegion;
                            r.Location = new PointF(r.Left, mStartRange);
                            mRenderContext.HandlerRegion = r;
                            mValue = mMinValue;
                            mMinRange = e.Y;
                            mIsOut = true;
                        }
                        else if (newPos > mEndRange)
                        {
                            RectangleF r = mRenderContext.HandlerRegion;
                            r.Location = new PointF(r.Left, mEndRange);
                            mRenderContext.HandlerRegion = r;
                            mValue = mMaxValue - LargeChange + 1;
                            mMaxRange = e.Y;
                            mIsOut = true;
                        }
                        else
                        {
                            RectangleF r = mRenderContext.HandlerRegion;
                            r.Location = new PointF(r.Left, newPos);
                            mRenderContext.HandlerRegion = r;
                            mValue = (int)((newPos - mStartRange) / mLengthFactor);
                            mIsOut = false;
                        }
                        mOldY = e.Y;
                    }
                }//VScrollbar
                #endregion IsVertical

                #region raize event
                if (mustRaiseEvent)
                {
                    if (oldValue < mValue)
                    {
                        ScrollEventArgs arg = new ScrollEventArgs(ScrollEventType.SmallIncrement, oldValue, mValue);
                        OnScroll(arg);
                        RaiseScroll(arg);
                    }
                    else if (oldValue > mValue)
                    {
                        ScrollEventArgs arg = new ScrollEventArgs(ScrollEventType.SmallDecrement, oldValue, mValue);
                        OnScroll(arg);
                        RaiseScroll(arg);
                    }
                    #region Defensive tracing
                    else
                    {
                        // both value are the same, do nothing.
                    }
                    #endregion
                }
                #region Defensive tracing
                else
                {
                    // not need to raise event.
                }
                #endregion
                #endregion
            }
            #endregion

            #region Otherwise
            else
            {
                ScrollerHitRegion newhit = HitTest(e.X, e.Y);
                if (newhit != mCurrentHit)
                {
                    mCurrentHit = newhit;
                }
                #region Defensive tracing
                else 
                {
                    // both value are the same.
                }
                #endregion

                if (!mIsDown)
                {
                    mRenderContext.StartButtonState = ButtonState.Normal;
                    mRenderContext.EndButtonState = ButtonState.Normal;
                    mRenderContext.HandlerState = ButtonState.Normal;
                    mRenderContext.ThirdButtonState = ButtonState.Normal;
                    switch (mCurrentHit)
                    {
                        case ScrollerHitRegion.Start:
                            mRenderContext.StartButtonState = ButtonState.Over;
                            break;
                        case ScrollerHitRegion.End:
                            mRenderContext.EndButtonState = ButtonState.Over;
                            break;
                        case ScrollerHitRegion.Third:
                            mRenderContext.ThirdButtonState = ButtonState.Over;
                            break;
                        case ScrollerHitRegion.Handler:
                            mRenderContext.HandlerState = ButtonState.Over;
                            break;
                        #region Defensive tracing
                        case ScrollerHitRegion.Track:
                        case ScrollerHitRegion.Other:
                        default:
                            // the mouse pointer does not belong to control, do nothing.
                            break;
                        #endregion
                    }
                }
                #region Defensive tracing
                else
                {
                    // mouse button is being pressed.
                }
                #endregion
            }
            #endregion

            InvalidateLayout();
        }
        #endregion

        #region Down
        /// <summary>
        /// Override this method for process the control when mouse button is pressed.
        /// </summary>
        protected override void OnMouseDown(MouseEventArgs e)
        {
            ScrollerHitRegion newhit = HitTest(e.X, e.Y);
            if (!mIsDown)
            {
                mIsDown = true;
                switch (newhit)
                {
                    #region Start
                    case ScrollerHitRegion.Start:
                        mRenderContext.StartButtonState = ButtonState.Pressed;
                        mUpDown = true;
                        mIsStopScroll = false;
                        DoScroll();
                        break;
                    #endregion

                    #region End
                    case ScrollerHitRegion.End:
                        mRenderContext.EndButtonState = ButtonState.Pressed;
                        mUpDown = false;
                        mIsStopScroll = false;
                        DoScroll();
                        break;
                    #endregion

                    #region Third
                    case ScrollerHitRegion.Third:
                        mRenderContext.ThirdButtonState = ButtonState.Pressed;
                        if (mShowThirdButtonAtStart)
                        {
                            mUpDown = true;
                        }
                        else
                        {
                            mUpDown = false;
                        }
                        mIsStopScroll = false;
                        DoScroll();
                        break;
                    #endregion

                    #region Handler
                    case ScrollerHitRegion.Handler:
                        mRenderContext.HandlerState = ButtonState.Pressed;
                        mOldX = e.X;
                        mOldY = e.Y;
                        if (mLayoutStyle == ScrollerStyle.Horizontal)
                        {
                            mMinRange = e.X;
                            mMaxRange = e.X;
                        }
                        else
                        {
                            mMinRange = e.Y;
                            mMaxRange = e.Y;
                        }                        
                        break;
                    #endregion

                    #region Track
                    case ScrollerHitRegion.Track:
                        RectangleF rHandler = mRenderContext.HandlerRegion;
                        mIsLargeChange = true;
                        mTargetX = e.X;
                        mTargetY = e.Y;
                        if (e.X < rHandler.Left || e.Y < rHandler.Top)
                        {
                            mUpDown = true;
                        }
                        else
                        {
                            mUpDown = false;
                        }
                        mIsStopScroll = false;
                        DoScroll();
                        break;
                    #endregion
                }
            }
            base.OnMouseDown(e);
        }
        #endregion

        #region Up
        /// <summary>
        /// Override this method for process the control when mouse button is up.
        /// </summary>
        protected override void OnMouseUp(MouseEventArgs e)
        {
            mIsStopScroll = true;
            if (mIsDown)
            {
                mIsDown = false;
                mIsLargeChange = false;
                mIsOut = false;
                mRenderContext.StartButtonState = ButtonState.Normal;
                mRenderContext.EndButtonState = ButtonState.Normal;
                mRenderContext.ThirdButtonState = ButtonState.Normal;
                mRenderContext.HandlerState = ButtonState.Normal;
                
                InvalidateLayout();
            }
            base.OnMouseUp(e);
        }
        #endregion

        #region Leave
        /// <summary>
        /// Override this method for process the control when mouse pointer move from inside to outside of the control.
        /// </summary>
        protected override void OnMouseLeave(EventArgs e)
        {
            base.OnMouseLeave(e);

            mCurrentHit = ScrollerHitRegion.Other;
            mRenderContext.StartButtonState = ButtonState.Normal;
            mRenderContext.EndButtonState = ButtonState.Normal;
            mRenderContext.HandlerState = ButtonState.Normal;
            mRenderContext.ThirdButtonState = ButtonState.Normal;

            InvalidateLayout();
        }
        #endregion

        #region Wheel
        /// <summary>
        /// Override this method for process the control when center button of mouse wheel.
        /// </summary>
        protected override void OnMouseWheel(MouseEventArgs e)
        {
            if (mAllowMouseWheel)
            {
                if (e.Delta > 0) ScrollTo(mValue - mSmallChange);
                else ScrollTo(mValue + mSmallChange);
            }
            #region Defensive tracing
            else
            {
                // Not allow mouse wheel process, do nothing.
            }
            #endregion
        }
        #endregion

        #region Process Mouse Wheel
        /// <summary>
        /// MouseWheel event is exposed so that other controls can bubble up
        /// mousewheel event to this scrollbar
        /// </summary>
        public void ProcessMouseWheel(MouseEventArgs e)
        {
            if (mAllowMouseWheel)
            {
                if (e.Delta > 0) ScrollTo(mValue - mSmallChange);
                else ScrollTo(mValue + mSmallChange);
            }
            #region Defensive tracing
            else
            {
                // mouse wheel not allow, do nothing.
            }
            #endregion
        }
        #endregion

        #endregion

        #region Process Key Events
        protected override bool ProcessDialogKey(Keys keyData)
        {
            bool handled = false;
            bool doProcess = true;
            if (DialogKeyPressed != null)
            {
                OnDialogKeyPressed(keyData);
                doProcess = RaiseDialogKeyPressed(keyData);
            }

            if (doProcess)
            {
                switch (keyData)
                {
                    case Keys.Up:
                    case Keys.Left:
                        ScrollTo(mValue - mSmallChange);
                        handled = true;
                        break;

                    case Keys.Down:
                    case Keys.Right:
                        ScrollTo(mValue + mSmallChange);
                        handled = true;
                        break;

                    case Keys.PageUp:
                        ScrollTo(mValue - mLargeChange);
                        handled = true;
                        break;

                    case Keys.PageDown:
                        ScrollTo(mValue + mLargeChange);
                        handled = true;
                        break;

                    case Keys.Home:
                        ScrollTo(mMinValue);
                        handled = true;
                        break;

                    case Keys.End:
                        ScrollTo(mMaxValue);
                        handled = true;
                        break;
                }
            }

            if (handled) return false;
            else
            {
                return base.ProcessDialogKey(keyData);
            }
        }
        #endregion

        //==========

        #region Constructors
        /// <summary>
        /// Default constructor.
        /// </summary>
        public Scroller() 
        {
            mRenderContext = new ScrollerRenderContext();
            //mTransparencyMethod = TransparencyMethod.TransparentWindows;
            this.Size = new Size(300, 18);
            SetStyle(ControlStyles.Selectable, false);
        }
        #endregion
    }

    #region Hit Region
    /// <summary>
    /// Define some regions which is used for identify the position
    /// of mouse pointer on the control.
    /// </summary>
    public enum ScrollerHitRegion
    {
        /// <summary>
        /// Button up if vertical scroller or button left if
        /// horizontal scroller.
        /// </summary>
        Start,
        /// <summary>
        /// Button down if vertical scroller or button right if
        /// horizontal scroller.
        /// </summary>
        End,
        /// <summary>
        /// Button next to the start button.
        /// </summary>
        Third,
        /// <summary>
        /// The handler which is used for handle and scroll.
        /// </summary>
        Handler,
        /// <summary>
        /// Whole region of the control without buttons.
        /// </summary>
        Track,
        /// <summary>
        /// The region not belong to the control.
        /// </summary>
        Other
    }
    #endregion

    #region Scroller Style
    /// <summary>
    /// Dimension of a specific scroller.
    /// </summary>
    public enum ScrollerStyle
    {
        /// <summary>
        /// Horizontal scroller.
        /// </summary>
        Horizontal = 0,
        /// <summary>
        /// Vertival scroller.
        /// </summary>
        Vertical = 1
    }
    #endregion
}